/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_kernel.htm
 *
 * Zhiqim Kernel is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.kernel.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.zhiqim.kernel.annotation.AnAlias;
import org.zhiqim.kernel.constants.CodeConstants;

/**
 * Zip/GZip 压缩与解压
 *
 * @version v1.0.0 @author zouzhigang 2014-2-27 新建与整理
 */
@AnAlias("Zips")
public class Zips implements CodeConstants
{
    /*****************************************************************************************/
    //Zip压缩和解压
    /*****************************************************************************************/
    
    /**
     * 压缩目录成zip文件
     * 
     * @param fileDirPath       文件目录路径
     * @param zipExportPath     zip文件路径
     * @throws Exception        异常
     */
    public static void zip(String fileDirPath, String zipExportPath) throws Exception
    {
        Asserts.notEmpty(fileDirPath, "文件目录路径");
        Asserts.notEmpty(zipExportPath, "ZIP导出路径");

        File fileDir = new File(fileDirPath);
        Asserts.as(fileDir.exists()?null:"文件目录路径不存在");

        //转化为/方式的绝对路径
        String fileDirCanonicalPath = Files.toLinuxPath(fileDir.getCanonicalPath());
        
        FileOutputStream fos = new FileOutputStream(zipExportPath);
        ZipOutputStream zos = new ZipOutputStream(fos, _UTF_8_C_);
        zos.setLevel(Deflater.BEST_COMPRESSION);
        
        List<File> fileList = new ArrayList<>();
        Files.queryFileList(fileList, fileDir, 0);
        
        for (File file : fileList)
        {
            String name = Files.toLinuxPath(file.getCanonicalPath());
            if (file.isDirectory())
                name = name.substring(fileDirCanonicalPath.length()+1) + "/";
            else
                name = name.substring(fileDirCanonicalPath.length()+1);
            
            ZipEntry ze = new ZipEntry(name);
            ze.setSize(file.length());
            ze.setTime(file.lastModified());
            zos.putNextEntry(ze);
            
            if (ze.isDirectory())
            {//目录
                zos.closeEntry();
            }
            else
            {//文件
                try (InputStream is = new FileInputStream(file))
                {
                    Streams.putBytes(is, zos);
                }
                zos.closeEntry();
            }
        }

        zos.close();
    }
    
    /**
     * 解压ZIP文件
     * 
     * @param zip            ZIP文件
     * @param toDir          解压目录
     * @return　             返回当前列表
     * @throws IOException   异常
     */
    public static List<File> unzip(File zip, File toDir) throws IOException 
    {
        List<File> fileList = new ArrayList<File>();
        ZipFile zipFile = new ZipFile(zip);
        Enumeration<?> entries = zipFile.entries();
        while (entries.hasMoreElements()) 
        {
            ZipEntry entry = (ZipEntry) entries.nextElement();
            if (entry.isDirectory()) 
            {
                Files.mkDirectory(new File(toDir, entry.getName()));
                continue;
            }

            InputStream input = null;
            OutputStream output = null;
            try 
            {
                File file = new File(toDir, entry.getName());
                
                input = zipFile.getInputStream(entry);
                output = new FileOutputStream(file);
                Streams.putBytes(input, output);
                
                fileList.add(file);
            } 
            finally 
            {
                Closes.closeIgnoreException(input, output);
            }
        }
        
        zipFile.close();
        return fileList;
    }
    
    
    /*****************************************************************************************/
    //GZip
    /*****************************************************************************************/
    
    /**
     * GZIP压缩，转化为正常输出流
     * 
     * @param output
     * @return
     * @throws IOException
     */
    public static ByteArrayOutputStream gzip(ByteArrayOutputStream output) throws IOException
    {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        
        try (GZIPOutputStream gzip = new GZIPOutputStream(bos);)
        {
            output.writeTo(gzip);
            gzip.finish();
            
            return bos;
        }
    }
    
    /**
     * GZIP压缩字符串，要求传入编码
     * 
     * @param src       原字符串
     * @param encoding  编码格式
     * @return 得到字节数组
     */
    public static byte[] gzipString(String src, String encoding) throws IOException
    {
        if (src == null)
            return new byte[0];
        
        try
        {
            return gzip(src.getBytes(encoding));
        }
        catch (UnsupportedEncodingException e)
        {
            return new byte[0];
        }
    }
    
    /**
     * GZIP字节压缩
     * 
     * @param src   原字节数组
     * @return 压缩全的字节数组
     */
    public static byte[] gzip(byte[] src) throws IOException
    {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        
        try (GZIPOutputStream gzip = new GZIPOutputStream(output))
        {
            gzip.write(src);
            gzip.finish();
            
            return output.toByteArray();
        }
    }
    
    /**
     * GZIP 解压字节数组
     * 
     * @param src           原字节数组
     * @return              得到解压后的字节数组
     * @throws IOException  IO异常需要捕捉
     */
    public static byte[] unGZip(byte[] src) throws IOException
    {
        ByteArrayInputStream input = new ByteArrayInputStream(src);
        
        try (GZIPInputStream gzip = new GZIPInputStream(input);)
        {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            Streams.putBytes(gzip, output);
            
            return output.toByteArray();
        }
    }
    
    /**
     * GZIP解压，转化为正常输入流
     * 
     * @param input         输入流
     * @return              正常的ByteArrayInputStream
     * @throws IOException  IO异常需要捕捉
     */
    public static ByteArrayInputStream unGZip(InputStream input) throws IOException
    {
        try (GZIPInputStream gzip = new GZIPInputStream(input);)
        {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            Streams.putBytes(gzip, out);
    
            return new ByteArrayInputStream(out.toByteArray());
        }
    }
    
    /**
     * GZIP解压，得到字符串
     * 
     * @param src       原字节数组
     * @param encoding  编码
     * @return          得到解压后的字节数组，再编码成字符串
     */
    public static String unGZipString(byte[] src, String encoding)
    {
        try
        {
            byte[] dest = unGZip(src);
            return new String(dest, encoding);
        }
        catch (UnsupportedEncodingException e)
        {
            throw Asserts.exception("非法的编码参数[%s]", encoding);
        }
        catch (IOException e) 
        {
            throw Asserts.exception(e);
        }
    }
    
    /**
     * GZIP解压文件到文件夹
     * 
     * @param gzip          解压文件
     * @param toDir         解压目录
     * @return              解压后的文件
     * @throws IOException
     */
    public static File unGZip(File gzip, File toDir) throws IOException 
    {
        Files.mkDirectory(toDir);
        File file = new File(toDir, gzip.getName());
        
        FileInputStream fis = null;
        FileOutputStream fos = null;
        GZIPInputStream input = null;
        
        try 
        {
            fis = new FileInputStream(gzip);
            fos = new FileOutputStream(file);
            
            input = new GZIPInputStream(fis);
            Streams.putBytes(input, fos);
        } 
        finally 
        {
            Closes.closeIgnoreException(fis, fos, input);
        }
        
        return file;
    }
}
